using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Utils;
using WebReferenceManager;
using WebReferenceManager.ServiceReference1;
using WindowsGame.Ui;
using WindowSystem;
using InputEventSystem;
using RoundLineCode;
using Utils.SoftReferences;
using GameObjects;
using MouseState=Microsoft.Xna.Framework.Input.MouseState;

namespace WindowsGame
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        public GraphicsDeviceManager Graphics;
        SpriteBatch spriteBatch;
        SpriteFont spriteFont;

        readonly UiModel _uiModel;

        /// <summary>Matrix used to transform 3D to screen coordinates</summary>
        private Matrix projection;

        GameState gameState;
        Faction player;
        readonly Cursor cursor;
        string pickedModelName;
        VertexPositionColor[] pickedTriangle =
        {
            new VertexPositionColor(Vector3.Zero, Color.Magenta),
            new VertexPositionColor(Vector3.Zero, Color.Magenta),
            new VertexPositionColor(Vector3.Zero, Color.Magenta),
        };

        BasicEffect lineEffect;
        VertexDeclaration lineVertexDeclaration;

        const float MagicalHexagonConstant = 0.86602540378443864676372317075294f;

        private ConnectToServer connectToServer;

        // To keep things efficient, the picking works by first applying a bounding
        // sphere test, and then only bothering to test each individual triangle
        // if the ray intersects the bounding sphere. This allows us to trivially
        // reject many models without even needing to bother looking at their triangle
        // data. This field keeps track of which models passed the bounding sphere
        // test, so you can see the difference between this approximation and the more
        // accurate triangle picking.
        List<string> insideBoundingSpheres = new List<string>();

        KeyboardState currentKeyboardState;
        GamePadState currentGamePadState;
        private MouseState currentMouseState;

        RoundLineManager roundLineManager;

        List<RoundLine> roads, unObservedRoads;
        public List<RoundLine> TempLines;
        List<RoundLine> moveLines;
        string[] roundLineTechniqueNames;

        // the following constants control how the camera's default position
        const float CameraDefaultDistance = 12.5f;

        Matrix viewMatrix;
        Matrix projectionMatrix;

        MapEditor mapEditor;
        public GameMode gameMode;

        List<VisibleObject> _visibleObjects;

        private float zoomDistance = CameraDefaultDistance;
        private int _scrollWheelZoom;
        Vector3 targetLookAt = Vector3.Zero;
        Vector3 _currentLookAt = Vector3.Zero;
        Vector3 _currentLookFrom = new Vector3(0f, 0f, CameraDefaultDistance);
        Vector3 deltaCurrentLookAt = Vector3.Zero;
        Vector3 deltaCurrentLookFrom = Vector3.Zero;

        const float lookAtMagicNumber = 5f;
        const float lookFromMagicNumber = 1.45f;

        public enum GameMode
        {
            PrePlay,
            Play,
            AwaitingNextTurn,
            EditMap,
            GameOver,
        }

        public const float HexSpacing = 3;

        private readonly GUIManager gui;
        private readonly InputEvents input;
        private MenuBar menuBar;
        private MenuItem newMenuItem;
        private MenuItem openMenuItem;
        //private MenuItem saveMenuItem;
        //private MenuItem saveAsMenuItem;
        private MenuItem exitMenuItem;
        private MenuItem serverConnectItem;
        private MenuItem mapEditMenuItem;
        //private MenuItem undoMenuItem;
        //private MenuItem redoMenuItem;
        private MenuItem displaySettingsItem;

        //TextButton endTurnButton;

        WebReferenceManager.ServerConnection serverConnection;

        private void OnMenuItemClicked(UIComponent sender)
        {
            if (sender == newMenuItem)
            {
                NewGame();
            }
            else if (sender == openMenuItem)
            {
                ContinueGame();
            }
            else if (sender == mapEditMenuItem)
            {
                if (gameMode == GameMode.Play)
                {
                    mapEditor = new MapEditor(this, gui, gameState);
                }
                else
                {
                    mapEditor.Close();
                }
            }
            else if (sender == serverConnectItem)
            {
                ConnectToServerDialog(serverConnection);
            }
            //else if (sender == undoMenuItem)
            //{
            //    var messageBox = new MessageBox(this, gui, "Undo clicked!", "Tutorial 1", MessageBoxButtons.OK, MessageBoxType.Info);
            //    messageBox.Show(true);
            //}
            //else if (sender == redoMenuItem)
            //{
            //    var messageBox = new MessageBox(this, gui, "Redo clicked!", "Tutorial 1", MessageBoxButtons.OK, MessageBoxType.Info);
            //    messageBox.Show(true);
            //}
            else if (sender == displaySettingsItem)
            {
                var displaySettings = new DisplaySettingsDialog(this, gui, Graphics);
                displaySettings.Show(false);
                displaySettings.Close += displaySettings_Close;
            }
            else if (sender == exitMenuItem)
                Exit();

        }

        private void ConnectToServerDialog(ServerConnection serverConnection)
        {
            if (connectToServer == null)
            {
                connectToServer = new ConnectToServer(this, gui, serverConnection);
                connectToServer.Close += serverConnect_Close;
            }
            connectToServer.Show(false);
            connectToServer.CenterWindow();
        }

        void serverConnect_Close(UIComponent sender)
        {
            serverConnection = ((ConnectToServer)sender).ServerConnection;
        }

        void displaySettings_Close(UIComponent sender)
        {
            AdjustControlsToNewScreenSize();
        }

        private void AdjustControlsToNewScreenSize()
        {
            menuBar.Width = Graphics.PreferredBackBufferWidth;
        }

        public bool NewGame()
        {
            if (serverConnection == null)
            {
                ConnectToServerDialog(null);
                return false;
            }

            var newGame = new NewGame(this, gui, serverConnection);
            newGame.Close += newGame_Close;
            return true;
        }

        public bool ContinueGame()
        {
            if (serverConnection == null)
            {
                ConnectToServerDialog(null);
                return false;
            }

            var continueGame = new ContinueGame(this, gui, serverConnection);
            continueGame.Close += continueGame_Close;
            return true;
        }

        private void ContinueGame(int gameId)
        {
            var openDialog = new ContinueGame(this, gui, serverConnection, gameId);
            openDialog.Close += continueGame_Close;
        }

        void newGame_Close(UIComponent sender)
        {
            var gameId = ((NewGame) sender).GameId;
            if (gameId != null)
                ContinueGame((int)gameId);
        }

        void continueGame_Close(UIComponent sender)
        {
            if (((ContinueGame)sender).GameName.SelectedItemKey == -1 || ((ContinueGame)sender).Faction.SelectedItemKey == -1)
                return;

            CreateGame(((ContinueGame)sender).GameName.SelectedItemKey, ((ContinueGame)sender).Faction.SelectedItemKey);
        }

        private GameDto gameDto;

        private void CreateGame(int gameId, int factionId)
        {
            gameDto = serverConnection.WebService.GetGame(gameId);

            if (gameDto.Id < 1)
                throw new ArgumentOutOfRangeException("gameId");
            if (factionId < 0)
                throw new ArgumentOutOfRangeException("factionId");

            var compressedGameState = serverConnection.WebService.GetInitialGameState(gameDto.Id);
            if (compressedGameState == null)
                throw new Exception("Error retreiving game state");

            gameState = (GameState)SerialiseHelper.DeserializeToObject(CompressHelper.Decompress(compressedGameState));//gameServer.CreateGame("TestMap", "blah");

            var gameOrders = serverConnection.WebService.GetAllOrders(gameDto.Id);

            gameOrders.OrderBy(o => o.Turn);

            foreach (var dto in gameOrders)
            {
                gameState.ProgressTurn(dto.Turn, GetUnitOrders(dto.UnitOrderDtos));
            }

            player = gameState.MapView.Factions.Single(o => o.Id == factionId);

            roundLineManager = new RoundLineManager();
            roundLineManager.Init(GraphicsDevice, Content);
            roundLineTechniqueNames = roundLineManager.TechniqueNames;

            LoadLines();

            _visibleObjects = new List<VisibleObject>();

            CreateHexes();
            CreateUnits();
            CreateStructures();
            

            gameMode = GameMode.Play;

            CheckGameOver();
        }

        private void CreateStructures()
        {
            var model = Content.Load<Model>("Square");
            var texture = Content.Load<Texture2D>("stone");

            _visibleObjects.RemoveAll(o => o is UiPoint);
            _uiModel.UiPoints = new List<UiPoint>();

            foreach (var structure in gameState.MapView.Structures)
            {
                if (player.DiscoveredHexes.Contains(structure.Hex))
                    _visibleObjects.Add(new UiPoint(_uiModel)
                                        {
                                            FriendlyId = "Structure " + structure.Id,
                                            Model = model,
                                            Texture = texture,
                                            Structure = new SoftRef<Structure>(structure),
                                        }
                    );
            }
        }

        private void CheckGameOver()
        {
            // Has turn limit been reached?
            if (gameState.Turn >= gameDto.TurnLimit)
            {
                var messageBox = new MessageBox(this, gui, "Turn limit of " + gameDto.TurnLimit + " has been reached.",
                                                "Game Over", MessageBoxButtons.OK, MessageBoxType.Info);
                messageBox.Show(true);
                gameMode = GameMode.GameOver;
            }

            // Is there a winner?
            if (gameState.GameOver)
            {
                // Who is the winner?
                var messageBox = gameState.Winner == player
                                     ? new MessageBox(this, gui, "You win!", "Game Over", MessageBoxButtons.OK, MessageBoxType.Info)
                                     : new MessageBox(this, gui, "You lose!", "Game Over", MessageBoxButtons.OK, MessageBoxType.Info);

                messageBox.Show(true);
                gameMode = GameMode.GameOver;
                serverConnection.WebService.EndGame(gameDto.Id);
            }
        }

        public Game1()
        {

            Graphics = new GraphicsDeviceManager(this)
                           {
                               PreferredBackBufferWidth = 1024,
                               PreferredBackBufferHeight = 768,
                           };

            Content.RootDirectory = "Content";
            cursor = new Cursor(this);
            Components.Add(cursor);

            _uiModel = new UiModel();

            // GUI stuff
            this.input = new InputEvents(this);
            Components.Add(this.input);

            this.gui = new GUIManager(this);
            Components.Add(this.gui);

            // Required for GUI. Dunno why.
            IsFixedTimeStep = false;

            gameMode = GameMode.PrePlay;
            //serverConnection = new WebReferenceManager.ServerConnection();

            
        }

        public Vector3 GetXYZForHexRowAndCol(float hexSpacing, int col, int row)
        {
            var vector2 = GetXYForHexRowAndCol(hexSpacing, col, row);

            return new Vector3(vector2.X, vector2.Y, 0);
        }

        public Vector2 GetXYForHexRowAndCol(float hexSpacing, int col, int row)
        {
            float x = (col / 2) * (hexSpacing) + (col % 2) * (hexSpacing / 2);
            float y = (Math.Abs(col % 2) * MagicalHexagonConstant) + (row * MagicalHexagonConstant * 2);

            return new Vector2(x, y);
        }

        UiHex selectedHex;
        List<UiUnit> highlightedUnits = new List<UiUnit>();
        UiUnit selectedUnit;

        /// <summary>
        /// Helper for drawing the outline of the triangle currently under the cursor.
        /// </summary>
        void DrawPickedTriangle()
        {
            if (pickedModelName == null) 
                return;
            
            GraphicsDevice device = Graphics.GraphicsDevice;
            RenderState renderState = device.RenderState;

            // Set line drawing renderstates. We disable backface culling
            // and turn off the depth buffer because we want to be able to
            // see the picked triangle outline regardless of which way it is
            // facing, and even if there is other geometry in front of it.
            renderState.FillMode = FillMode.WireFrame;
            renderState.CullMode = CullMode.None;
            renderState.DepthBufferEnable = false;

            // Activate the line drawing BasicEffect.
            lineEffect.Projection = projectionMatrix;
            lineEffect.View = viewMatrix;

            lineEffect.Begin();
            lineEffect.CurrentTechnique.Passes[0].Begin();

            // Draw the triangle.
            device.VertexDeclaration = lineVertexDeclaration;

            device.DrawUserPrimitives(PrimitiveType.TriangleList,
                                      pickedTriangle, 0, 1);

            lineEffect.CurrentTechnique.Passes[0].End();
            lineEffect.End();

            // Reset renderstates to their default values.
            renderState.FillMode = FillMode.Solid;
            renderState.CullMode = CullMode.CullCounterClockwiseFace;
            renderState.DepthBufferEnable = true;
        }

        /// <summary>
        /// Helper for drawing text showing the current picking results.
        /// </summary>
        void DrawText()
        {
            // Draw the text twice to create a drop-shadow effect, first in black one
            // pixel down and to the right, then again in white at the real position.
            var shadowOffset = new Vector2(1, 1);

            spriteBatch.Begin();

            if (gameState != null)
            {
                WriteTextLine("Time Remaining: " + TimeSpanHelper.ConvertToString(TimeLeft()), new Vector2(20, Graphics.PreferredBackBufferHeight - 35));
                WriteTextLine("Turn: " + gameState.Turn + " / " + gameDto.TurnLimit, new Vector2(20, Graphics.PreferredBackBufferHeight - 60));
                WriteTextLine("Season: " + gameState.Season, new Vector2(150, Graphics.PreferredBackBufferHeight - 60));
                WriteTextLine("Faction: " + player.Name, new Vector2(20, Graphics.PreferredBackBufferHeight - 85));
                WriteTextLine("Mode: " + gameMode, new Vector2(20, Graphics.PreferredBackBufferHeight - 110));
                WriteTextLine("Game: " + gameState.Name, new Vector2(20, Graphics.PreferredBackBufferHeight - 135));
                //WriteTextLine(currentMouseState.ScrollWheelValue.ToString(), new Vector2(20, Graphics.PreferredBackBufferHeight - 165));
                
                if (highlightedUnits.Count > 0 || selectedUnit != null)
                {
                    var unitDetailed = (selectedUnit ?? highlightedUnits.First()).Unit.Target;

                    WriteTextLine("Unit: " + unitDetailed.Name, new Vector2(20, 75));
                    WriteTextLine("Owner: " + unitDetailed.Owner.Name, new Vector2(20, 100));
                    WriteTextLine("Move Type: " + unitDetailed.MoveType, new Vector2(20, 125));
                    WriteTextLine("Move Limit: " + gameState.UnitMoveLimit(unitDetailed), new Vector2(20, 150));
                    WriteTextLine("Preferred Terrain: " + unitDetailed.PreferredTerrain ?? unitDetailed.PreferredTerrain.Name, new Vector2(20, 175));
                    WriteTextLine("Strength: " + gameState.UnitStrengthStatus(unitDetailed) + " - " + Math.Round(gameState.UnitStrength(unitDetailed), 1) + " (" + gameState.UnitStrengthCalculationWorking(unitDetailed) + ")", new Vector2(20, 200));
                    WriteTextLine("Morale: " + unitDetailed.MoraleStatus, new Vector2(20, 225));
                    WriteTextLine("Vision: " + unitDetailed.Vision, new Vector2(20, 250));
                    WriteTextLine("Concealment Terrain: " + unitDetailed.ConcealmentTerrain ?? unitDetailed.ConcealmentTerrain.Name, new Vector2(20, 275));
                    WriteTextLine("Hindered by Cold Weather: " + unitDetailed.HinderedByColdWeather, new Vector2(20, 300));

                    var y = 75;
                    foreach (var unitMoveCost in unitDetailed.MovementTable)
                    {
                        WriteTextLine(unitMoveCost.TerrainType.Name + ": " + unitMoveCost.MovementCost,
                                      new Vector2(800, y));
                        y += 25;
                    }
                }
            }

            // Draw a list of which models passed the initial bounding sphere test.
            if (insideBoundingSpheres.Count > 0)
            {
                //var text = "Inside bounding sphere: " + string.Join(", ", insideBoundingSpheres.ToArray());

                //var position = new Vector2(20, 25);

                //WriteTextLine(text, position);

                //spriteBatch.DrawString(spriteFont, text,
                //                       position + shadowOffset, Color.Black);

                //spriteBatch.DrawString(spriteFont, text,
                //                       position, Color.White);
            }

            // Draw the name of the model that passed the per-triangle picking test.
            if (pickedModelName != null)
            {
                if (selectedHex != null)
                {
                    WriteTextLine(selectedHex.Hex.Target.X + ", " + selectedHex.Hex.Target.Y, new Vector2(20, 25));
                    if (player.DiscoveredHexes.Contains(selectedHex.Hex.Target))
                    {
                        WriteTextLine(gameState.HexTerrainType(selectedHex.Hex.Target).Name, new Vector2(100, 25));
                        WriteTextLine("Weather: " + gameState.GetWeather(selectedHex.Hex.Target), new Vector2(200, 25));
                        WriteTextLine("Stack Limit: " + selectedHex.Hex.Target.StackLimit, new Vector2(350, 25));
                        if (selectedHex.Hex.Target.Structure != null)
                            WriteTextLine("Structure: " + selectedHex.Hex.Target.Structure.StructureType,
                                          new Vector2(550, 25));
                    }
                }


                //Vector2 position = cursor.Position;

                //// Draw the text below the cursor position.
                //position.Y += 32;

                //// Center the string.
                //position -= spriteFont.MeasureString(pickedModelName) / 2;

                //spriteBatch.DrawString(spriteFont, pickedModelName, position + shadowOffset, Color.Black);
                //spriteBatch.DrawString(spriteFont, pickedModelName, position, Color.White);
            }

            spriteBatch.End();
        }

        private void WriteTextLine(string text, Vector2 vector2)
        {
            spriteBatch.DrawString(spriteFont, text, vector2, Color.Black);
            spriteBatch.DrawString(spriteFont, text, vector2 + new Vector2(1, 1), Color.White);
        }

        /// <summary>
        /// Runs a per-triangle picking algorithm over all the models in the scene,
        /// storing which triangle is currently under the cursor.
        /// </summary>
        void UpdatePicking()
        {
            if (cursor == null)
                return;

            // Look up a collision ray based on the current cursor position. See the
            // Picking Sample documentation for a detailed explanation of this.
            var cursorRay = cursor.CalculateCursorRay(projectionMatrix, viewMatrix);

            // Clear the previous picking results.
            insideBoundingSpheres.Clear();

            pickedModelName = null;

            // Keep track of the closest object we have seen so far, so we can
            // choose the closest one if there are several models under the cursor.
            var closestIntersection = float.MaxValue;

            highlightedUnits = new List<UiUnit>();

            // Loop over all our models.
            foreach (var obj in _visibleObjects)
            {
                bool insideBoundingSphere;
                Vector3 vertex1, vertex2, vertex3;

                // Perform the ray to model intersection test.
                var intersection = PickingHelper.RayIntersectsModel(cursorRay, obj.Model,
                                                         obj.WorldTransform,
                                                         out insideBoundingSphere,
                                                         out vertex1, out vertex2, out vertex3);

                // If this model passed the initial bounding sphere test, remember
                // that so we can display it at the top of the screen.
                if (insideBoundingSphere)
                {
                    insideBoundingSpheres.Add(obj.FriendlyId);
                }

                // Do we have a per-triangle intersection with this model?
                if (intersection == null)
                    continue;
                
                // If so, is it closer than any other model we might have
                // previously intersected?
                // previously intersected?
                if (intersection >= closestIntersection)
                    continue;
                
                // Store information about this model.
                closestIntersection = intersection.Value;

                pickedModelName = obj.FriendlyId;

                if (obj is UiHex)
                {
                    selectedHex = obj as UiHex;
                }
                else if (obj is UiUnit)
                    highlightedUnits.Add(obj as UiUnit);

                // Store vertex positions so we can display the picked triangle.
                pickedTriangle[0].Position = vertex1;
                pickedTriangle[1].Position = vertex2;
                pickedTriangle[2].Position = vertex3;
            }
        }

        /// <summary>
        /// Handles input for moving the camera.
        /// </summary>
        void UpdateCamera(GameTime gameTime)
        {
            float leftX = 0;// = gamePadState.ThumbSticks.Left.X;
            if (currentKeyboardState.IsKeyDown(Keys.A) || currentKeyboardState.IsKeyDown(Keys.Left))
                leftX -= 1.0f;
            if (currentKeyboardState.IsKeyDown(Keys.D) || currentKeyboardState.IsKeyDown(Keys.Right))
                leftX += 1.0f;

            float leftY = 0;// = gamePadState.ThumbSticks.Left.Y;
            if (currentKeyboardState.IsKeyDown(Keys.W) || currentKeyboardState.IsKeyDown(Keys.Up))
                leftY += 1.0f;
            if (currentKeyboardState.IsKeyDown(Keys.S) || currentKeyboardState.IsKeyDown(Keys.Down))
                leftY -= 1.0f;            

            const float viewPointMoveRate = 0.5f;

            var dx = leftX * viewPointMoveRate;
            var dy = leftY * viewPointMoveRate;
            //var dz = leftZ * viewPointMoveRate;

            targetLookAt.X += dx;
            targetLookAt.Y += dy;
            //targetLookAt.Z += dz;

            //Matrix unrotatedView = Matrix.CreateLookAt(
            //    new Vector3(0, 0, -cameraDistance), Vector3.Zero, Vector3.Up);

            //viewMatrix = Matrix.CreateTranslation(-cameraX, -cameraY, 0) * Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
            //              Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
            //              unrotatedView;

            // deltaCurrentLookAt is the rate that the camera look at point is moving per second.
            // we use it so that the camera look at point moves smoothly to the goal target point.

            deltaCurrentLookAt = ((targetLookAt - _currentLookAt) * lookAtMagicNumber);

            var targetLookFrom = targetLookAt;

            const bool makeSeasick = true;

            if (makeSeasick)
            {
                targetLookFrom.X += (float)Math.Cos(gameTime.TotalGameTime.TotalSeconds * 0.2f) * 3f;
                targetLookFrom.Y += (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds * 0.2f) * 3f;
            }

            float zoom = 0;
            if (currentMouseState.ScrollWheelValue > _scrollWheelZoom)
            {
                zoom = -4f;
                _scrollWheelZoom = currentMouseState.ScrollWheelValue;
            }
            else if (currentMouseState.ScrollWheelValue < _scrollWheelZoom)
            {
                zoom = 4f;
                _scrollWheelZoom = currentMouseState.ScrollWheelValue;
            }
            zoomDistance += zoom;

            zoomDistance = MathHelper.Clamp(zoomDistance, 5, 25);

            targetLookFrom.Z = zoomDistance;

            deltaCurrentLookFrom = ((targetLookFrom - _currentLookFrom) * lookFromMagicNumber);   

            _currentLookAt += deltaCurrentLookAt * (float)gameTime.ElapsedGameTime.TotalSeconds;
            _currentLookFrom += deltaCurrentLookFrom * (float)gameTime.ElapsedGameTime.TotalSeconds;

            var unrotatedView = Matrix.CreateLookAt(_currentLookFrom, _currentLookAt, Vector3.Up);

            viewMatrix = unrotatedView;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();

            _visibleObjects = new List<VisibleObject>();

            var aspectRatio = Graphics.GraphicsDevice.Viewport.Width / (float)Graphics.GraphicsDevice.Viewport.Height;

            // ... and use that value to create the projection matrix.
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, .01f, 1000);

            projection = Matrix.CreatePerspectiveFieldOfView(
                                                    MathHelper.PiOver4,
                                                    Window.ClientBounds.Width / (float)Window.ClientBounds.Height,
                                                    0.01f, 1000.0f);


            this.menuBar = new MenuBar(this, gui);
            var fileMenu = new MenuItem(this, gui) { Text = "Game" };
            this.newMenuItem = new MenuItem(this, gui) { Text = "New..." };
            fileMenu.Add(this.newMenuItem);
            this.openMenuItem = new MenuItem(this, gui) { Text = "Continue..." };
            fileMenu.Add(this.openMenuItem);
            //this.saveMenuItem = new MenuItem(this, gui) { Text = "Save", IsEnabled = false };
            //fileMenu.Add(this.saveMenuItem);
            //this.saveAsMenuItem = new MenuItem(this, gui) { Text = "Save As...", IsEnabled = false };
            //fileMenu.Add(this.saveAsMenuItem);
            this.exitMenuItem = new MenuItem(this, gui) { Text = "Exit" };
            fileMenu.Add(this.exitMenuItem);
            menuBar.Add(fileMenu);
            var editMenu = new MenuItem(this, gui) { Text = "Edit" };
            //undoMenuItem = new MenuItem(this, gui) { Text = "Undo" };
            //editMenu.Add(this.undoMenuItem);
            //redoMenuItem = new MenuItem(this, gui) { Text = "Redo" };
            //editMenu.Add(this.redoMenuItem);
            serverConnectItem = new MenuItem(this, gui) { Text = "Connect to Server" };
            editMenu.Add(serverConnectItem);
            mapEditMenuItem = new MenuItem(this, gui) { Text = "Map" };
            editMenu.Add(mapEditMenuItem);
            displaySettingsItem = new MenuItem(this, gui) { Text = "Display Settings" };
            editMenu.Add(displaySettingsItem);

            menuBar.Add(editMenu);

            // Add menubar to gui
            this.gui.Add(this.menuBar);

            // Add event handlers
            newMenuItem.Click += OnMenuItemClicked;
            openMenuItem.Click += OnMenuItemClicked;
            //this.saveMenuItem.Click += OnMenuItemClicked;
            //this.saveAsMenuItem.Click += OnMenuItemClicked;
            exitMenuItem.Click += OnMenuItemClicked;
            serverConnectItem.Click += OnMenuItemClicked;
            mapEditMenuItem.Click += OnMenuItemClicked;
            //this.undoMenuItem.Click += OnMenuItemClicked;
            //this.redoMenuItem.Click += OnMenuItemClicked;
            displaySettingsItem.Click += OnMenuItemClicked;

            new Start(this, gui);
            try
            {
                serverConnection = new ServerConnection();
            }
            catch (Exception)
            {
                ConnectToServerDialog(null);
            }
        }

        private void CreateUnits()
        {
            var model = Content.Load<Model>("Pentagon");
            var texture = Content.Load<Texture2D>("elf");

            _visibleObjects.RemoveAll(o => o is UiUnit);
            _uiModel.UiUnits = new List<UiUnit>();

            foreach (var unit in gameState.ActiveUnits)
            {
                if (!gameState.UnitVisible(player, unit))
                    continue;

                _visibleObjects.Add(new UiUnit(_uiModel)
                                        {
                                            FriendlyId = "Unit " + unit.Id,
                                            Model = model,
                                            Texture = texture,
                                            Unit = new SoftRef<Unit>(unit)
                                        }
                    );
            }
        }

        public void CreateHexes()
        {
            var hexagonModel = Content.Load<Model>("Hexagon");
            var hexagonTexture = Content.Load<Texture2D>("rocks");

            _visibleObjects.RemoveAll(o => o is UiHex);
            _uiModel.UiHexes = new List<UiHex>();

            //for (var i = 0; i < gameState.Board.Width; i++)
            //{
                foreach (var hex in gameState.MapView.Board.Hexes)
                {
                    //var hex = new SoftRef<Hex>(gameState.Board[i, j]);

                    _visibleObjects.Add(
                        new UiHex(_uiModel)
                            {
                                Texture = hexagonTexture,
                                FriendlyId = "Hex @ " + hex.X + ", " + hex.Y,
                                Model = hexagonModel,
                                Hex = new SoftRef<Hex>(hex),
                                DiffuseColor =
                                    player.DiscoveredHexes.Contains(hex)
                                        ? player.ObservedHexes.Contains(hex) ? ColourHelper.FromHexString(gameState.HexTerrainType(hex).Colour) : ColourHelper.FromHexString(gameState.HexTerrainType(hex).Colour) - new Vector3(.5f, .5f, .5f)
                                        : ColourHelper.FromHexString("333333"),
                        }
                    );
                }
            //}
        }

        //void endTurnButton_Click(UIComponent sender)
        //{
            

        //    //var result = 
        //        //serverConnection.SendOrders(GameId, gameState.Turn, player.Id, player.Orders);

        //    //if (result)
        //    //{
        //        gameMode = GameMode.AwaitingNextTurn;
        //        //lastProcessedSecond = -1;
        //    //}

        //    //return;

        //    //CreateGame(GameId, player.Id);
        //}

        //private List<WebReferenceManager.ServiceReference1.UnitOrderDto> Fudge(List<UnitOrderDto> list)
        //{
        //    var f = new List<WebReferenceManager.ServiceReference1.UnitOrderDto>();
        //    foreach (var b in list)
        //        f.Add(new WebReferenceManager.ServiceReference1.UnitOrderDto {UnitId = b.UnitId, Hexes = (int[])b.Hexes} );
        //    return f;
        //}

        TimeSpan TimeLeft()
        {
            return gameDto.TurnEnds - DateTime.Now;
        }

        void CheckForNextTurn()
        {
            if (gameMode == GameMode.Play)
                if (TimeLeft() < TimeSpan.Zero)
                    gameMode = GameMode.AwaitingNextTurn;

            if (gameMode == GameMode.AwaitingNextTurn)
            {
                if (!serverConnection.NextTurn(gameDto.Id, gameState.Turn + 1))
                    return;

                if (serverConnection.DtoOrders != null)
                {
                    gameState.ProgressTurn(gameState.Turn, GetUnitOrders(serverConnection.DtoOrders));

                    serverConnection.DtoOrders = null;

                    CreateUnits();
                    CreateHexes();
                    CreateStructures();

                    LoadLines();
                    
                    gameDto = serverConnection.WebService.GetGame(gameDto.Id);

                    gameMode = GameMode.Play;

                    CheckGameOver();

                    moveLines = new List<RoundLine>();
                }
                else
                    serverConnection.GetOrders(gameDto.Id, gameState.Turn);
            }
            //CreateGame(gameDto, player.Id);
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            spriteFont = Content.Load<SpriteFont>("Fonts/Verdana");

            //myFont = Content.Load<SpriteFont>("Fonts/Verdana");
            //skin = Content.Load<Skin>("DefaultSkin");

            lineEffect = new BasicEffect(Graphics.GraphicsDevice, null) {VertexColorEnabled = true};

            lineVertexDeclaration = new VertexDeclaration(Graphics.GraphicsDevice,
                                                VertexPositionColor.VertexElements);

        }

        public void LoadLines()
        {
            roads = new List<RoundLine>();
            unObservedRoads = new List<RoundLine>();
            TempLines = new List<RoundLine>();

            foreach (var vector in gameState.MapView.Vectors)
            {
                if (!player.DiscoveredHexes.Contains(vector.Nodes[0]) && !player.DiscoveredHexes.Contains(vector.Nodes[1]))
                    continue;

                var node0 = GetXYZForHexRowAndCol(HexSpacing, vector.Nodes[0].X, vector.Nodes[0].Y);
                var node1 = GetXYZForHexRowAndCol(HexSpacing, vector.Nodes[1].X, vector.Nodes[1].Y);

                var spazFactor = vector.Nodes[0].SpazFactor;
                if (vector.Nodes[1].SpazFactor > spazFactor)
                    spazFactor = vector.Nodes[1].SpazFactor;

                if (!player.ObservedHexes.Contains(vector.Nodes[0]) && !player.ObservedHexes.Contains(vector.Nodes[1]))
                    unObservedRoads.AddRange(CurvyLine.GetCurvyLine(node0, node1, spazFactor, vector.Seed));
                else
                    roads.AddRange(CurvyLine.GetCurvyLine(node0, node1, spazFactor, vector.Seed));
            }
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // Once a second
            //if (gameMode == GameMode.AwaitingNextTurn && (int)gameTime.TotalGameTime.TotalSeconds > lastProcessedSecond + 5)
            //{
            //    lastProcessedSecond = (int)gameTime.TotalGameTime.TotalSeconds;

            CheckForNextTurn();
            CheckForNewOrders();
//                _skittishUnit.Unit.Target.Cell = gameState.Board[random.Next(5), random.Next(5)];
            //}

            HandleInput();

            _uiModel.RebuildHexUnitRelationships();

            foreach (var vo in _visibleObjects)
            {
                if (vo is UiUnit)
                { 
                    var unit = (UiUnit)vo;
                    ((UiUnit)vo).Update(gameTime, gameState.UnitsInHex(unit.UiHex.Hex.Target).Count(), unit.UiHex.UnitPositionMaker++);
                }
                else
                    vo.Update(gameTime);
            }

            foreach (var uiHex in _uiModel.UiHexes)
                uiHex.UnitPositionMaker = 0;

            UpdateCamera(gameTime);

            UpdatePicking();

            base.Update(gameTime);
        }

        private List<UnitMoveOrder> GetUnitOrders(IEnumerable<UnitOrderDto> orderDtos)
        {
            var orders = new List<UnitMoveOrder>();
            foreach (var dtoOrder in orderDtos)
            {
                var order = new UnitMoveOrder(gameState.ActiveUnits.First(o => o.Id == dtoOrder.UnitId));

                //if (player != null && order.Unit.Owner == player)
                //{
                    foreach (var hexId in dtoOrder.Hexes)
                    {
                        order.Hexes.Add(gameState.MapView.Board.Hexes.First(o => o.Id == hexId));
                    }
                    orders.Add(order);
                //}
            }
            return orders;
        }

        private List<UnitOrderDto> dtoOrders;

        private void CheckForNewOrders()
        {
            if (gameMode == GameMode.Play)
            {
                if (serverConnection.DtoOrders != null && serverConnection.DtoOrders != dtoOrders)
                {
                    dtoOrders = serverConnection.DtoOrders;

                    player.Orders = GetUnitOrders(dtoOrders).Where(o => o.Unit.Owner == player).ToList();

                    moveLines = GenerateMoveLines(player.Orders);

                    //serverConnection.DtoOrders = null;
                }
                else
                {
                    serverConnection.GetOrders(gameDto.Id, gameState.Turn);
                }
            }
        }

        private UnitMoveOrder tempUnitOrder;
        void HandleInput()
        {
            currentKeyboardState = Keyboard.GetState();
            currentGamePadState = GamePad.GetState(PlayerIndex.One);
            currentMouseState = Mouse.GetState();

            switch (gameMode)
            {
                case GameMode.EditMap:
                    mapEditor.Input(cursor, _uiModel.HighlightedHexes);
                    break;
                case GameMode.Play:
                    if (highlightedUnits.FirstOrDefault() != null)
                    {
                        if (cursor.MouseButtonRelease(MouseButtons.LeftButton))
                        {
                            _visibleObjects.Remove(
                                _visibleObjects.FirstOrDefault(o => o.GetType() == typeof (UiUnitSelection)));

                            if (selectedUnit == highlightedUnits.FirstOrDefault())
                            {
                                selectedUnit = null;
                                _uiModel.HighlightedHexes = new List<UiHex>();
                            }
                            else if (highlightedUnits.FirstOrDefault().Unit.Target.Owner == player)
                            {

                                selectedUnit = highlightedUnits.FirstOrDefault();
                                tempUnitOrder = null;

                                var unitWorldCoords = GetXYForHexRowAndCol(HexSpacing,
                                                                           selectedUnit.UiHex.Hex.Target.X,
                                                                           selectedUnit.UiHex.Hex.Target.Y);

                                targetLookAt.X = unitWorldCoords.X;
                                targetLookAt.Y = unitWorldCoords.Y;

                                _visibleObjects.Add(new UiUnitSelection(_uiModel)
                                                        {
                                                            FriendlyId = "Selected Unit",
                                                            Model = Content.Load<Model>("SelectedPentagon"),
                                                            //Texture = Content.Load<Texture2D>("elf"),
                                                            //WorldTransform = Matrix.CreateScale(0.3f) * Matrix.CreateTranslation(0.2f, 0.2f, 0.1f),
                                                            UiUnit = selectedUnit,
                                                            //DiffuseColor = Blue,
                                                        });

                                _uiModel.HighlightedHexes = new List<UiHex>();


                                var possibleMoves = gameState.GetMoveList(selectedUnit.Unit.Target);
                                foreach (var move in possibleMoves.Where(o => o.Cost != MetaData.MovementCost.OneToFlyOver))
                                    foreach (var obj in _visibleObjects)
                                        if (obj is UiHex && ((UiHex) obj).Hex.Target == move.Destination)
                                            _uiModel.HighlightedHexes.Add(obj as UiHex);

                            }
                        }
                    }
                    if (selectedUnit != null && selectedHex != null)
                    {
                        if (currentKeyboardState.IsKeyDown(Keys.Delete) || cursor.MouseButtonRelease(MouseButtons.RightButton))
                        {
                            serverConnection.DeleteOrder(gameDto.Id, gameState.Turn, selectedUnit.Unit.TargetId);

                            selectedUnit = null;
                            _visibleObjects.Remove(_visibleObjects.FirstOrDefault(o => o.GetType() == typeof(UiUnitSelection)));

                            _uiModel.HighlightedHexes = new List<UiHex>();
                        }
                        else if (cursor.MouseButtonRelease(MouseButtons.LeftButton))
                        {
                            if (_uiModel.HighlightedHexes.Contains(selectedHex))
                            {
                                var destinationMove = selectedUnit.Unit.Target.MoveList.FirstOrDefault(o => o.Destination == selectedHex.Hex.Target);

                                if (destinationMove == null)
                                    break;

                                var order = tempUnitOrder;

                                if (order == null)
                                {
                                    order = tempUnitOrder ?? new UnitMoveOrder(selectedUnit.Unit.Target);

                                    if (player.Orders.SingleOrDefault(o => o.Unit == selectedUnit.Unit.Target) != null)
                                    {
                                        player.Orders.Remove(
                                            player.Orders.Single(o => o.Unit == selectedUnit.Unit.Target));
                                    }

                                    AddMoveStuff(order, destinationMove);
                                }
                                
                                if (order.Unit.MoveList.Any(o => o.Cost == MetaData.MovementCost.OneToFlyOver && o.PreviousMove.Destination == order.Hexes[order.Hexes.Count - 1] && o.Destination == order.Hexes.Last()))
                                    break;

                                player.Orders.Add(order);

                                serverConnection.SendOrder(gameDto.Id, gameState.Turn, player.Id, order);

                                TempLines = new List<RoundLine>();

                                //GenerateMoveLines(player.Orders);

                                //selectedUnit.Unit.Target.Cell = selectedHex.Cell.Target;
                                //selectedUnit.Unit.Target.MoveList = new List<Move>();

                                selectedUnit = null;
                                _visibleObjects.Remove(
                                    _visibleObjects.FirstOrDefault(o => o.GetType() == typeof(UiUnitSelection)));
                                _uiModel.HighlightedHexes = new List<UiHex>();
                            }
                        }
                        else if (currentKeyboardState.IsKeyDown(Keys.LeftControl) || currentKeyboardState.IsKeyDown(Keys.RightControl))
                        {
                            if (tempUnitOrder == null || selectedUnit.UiHex == selectedHex)
                            {
                                tempUnitOrder = new UnitMoveOrder(selectedUnit.Unit.Target) { Hexes = new List<Hex>() { selectedHex.Hex.Target } };
                            }
                            else if (!tempUnitOrder.Hexes.Contains(selectedHex.Hex.Target))
                            {
                                var move =
                                    selectedUnit.Unit.Target.MoveList.SingleOrDefault(
                                        o => o.PreviousMove.Destination == tempUnitOrder.Hexes.Last() &&
                                        o.Destination == selectedHex.Hex.Target);

                                if (move == null)
                                    break;

                                //if (move.MovesRemaining < move.PreviousMove.MovesRemaining)
                                //{
                                if (tempUnitOrder.Hexes.Count > 1)
                                {
                                    var lastMove =
                                        selectedUnit.Unit.Target.MoveList.Single(
                                            o =>
                                            o.Destination == tempUnitOrder.Hexes.Last() &&
                                            o.PreviousMove.Destination == tempUnitOrder.Hexes[tempUnitOrder.Hexes.Count - 2]
                                            );
                                    if (lastMove.Cost == MetaData.MovementCost.One && tempUnitOrder.Hexes.Count <= gameState.UnitMoveLimit(selectedUnit.Unit.Target))
                                        tempUnitOrder.Hexes.Add(selectedHex.Hex.Target);
                                }
                                else
                                {
                                    tempUnitOrder.Hexes.Add(selectedHex.Hex.Target);
                                }
                                //    tempUnitOrder.Hexes.Add(selectedHex.Hex.Target);
                                //}
                            }
                            else if (tempUnitOrder.Hexes.Contains(selectedHex.Hex.Target) && tempUnitOrder.Hexes.Last() != selectedHex.Hex.Target)
                                tempUnitOrder.Hexes.Remove(tempUnitOrder.Hexes.Last());

                            TempLines = GenerateMoveLines(new List<UnitMoveOrder> { tempUnitOrder });
                        }
                        else if (currentKeyboardState.IsKeyUp(Keys.LeftControl) && currentKeyboardState.IsKeyUp(Keys.RightControl))
                        {
                            tempUnitOrder = null;
                            TempLines = new List<RoundLine>();
                        }
                    }
                    break;
            }

            // Check for exit.
            if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
                currentGamePadState.Buttons.Back == ButtonState.Pressed)
            {
                Exit();
            }
        }

        private List<RoundLine> GenerateMoveLines(IEnumerable<UnitMoveOrder> list)
        {
            var lines = new List<RoundLine>();

            foreach (var order in list)
            {
                if (order.Hexes.Count == 0)
                    continue;

                var startPosition = _uiModel.UiUnits.Single(o => o.Unit.Target == order.Unit);
                var destinationPosition = _uiModel.UiHexes.Single(o => o.Hex.Target == order.Unit.Location);

                var positionOffsetV3 = (startPosition.WorldTransform.Translation - destinationPosition.WorldTransform.Translation);

                var positionOffset = new Vector2(positionOffsetV3.X, positionOffsetV3.Y);

                var currentHex = order.Hexes.Last();

                while (order.Hexes.IndexOf(currentHex) - 1 >= 0)
                {
                    var nextHext = order.Hexes[order.Hexes.IndexOf(currentHex) - 1];
                    var destinationVector = GetXYForHexRowAndCol(HexSpacing, currentHex.X, currentHex.Y) + positionOffset;
                    var originVector = GetXYForHexRowAndCol(HexSpacing, nextHext.X, nextHext.Y) + positionOffset;

                    var colour = Color.Blue;
                    colour.A = 192;
                    lines.Add(new RoundLine(destinationVector, originVector));

                    currentHex = nextHext;
                }
            }

            return lines;
        }

        private void AddMoveStuff(UnitMoveOrder unitOrder, Move destinationMove)
        {
            //gameState.UnitOrders.Add(new UnitOrder(unit, destinationMove));

            var c = destinationMove;
            unitOrder.Hexes = new List<Hex>();

            while (c != null)
            {
                unitOrder.Hexes.Insert(0, c.Destination);

                c = c.PreviousMove;
            }
                //AddMoveLine(destinationMove);
        }

        //private void AddMoveLine(Move move)
        //{
        //    if (move.PreviousMove == null)
        //        return;

        //    var destinationVector = GetXYForHexRowAndCol(HexSpacing, move.Destination.X, move.Destination.Y);
        //    var originVector = GetXYForHexRowAndCol(HexSpacing, move.PreviousMove.Destination.X, move.PreviousMove.Destination.Y);

        //    if (move.PreviousMove.PreviousMove == null)
        //        originVector = GetXYForHexRowAndCol(HexSpacing, move.PreviousMove.Destination.X, move.PreviousMove.Destination.Y);


        //    moveLines.Add(new RoundLine(destinationVector, originVector));
        //    AddMoveLine(move.PreviousMove);
        //}
        //private void DrawModel(Model model, Matrix worldTransform)
        //{
        //    DrawModel(model, worldTransform, new Vector3(1f, 1f, 1f), 1f, null);
        //}

        private void DrawModel(Model model, Matrix worldTransform, Vector3 diffuseColor, float alpha, Texture2D texture)
        {
            // nothing tricky in here; this is the same model drawing code that we see
            // everywhere. we'll loop over all of the meshes in the model, set up their
            // effects, and then draw them.
            foreach (var mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();

                    effect.DiffuseColor = diffuseColor;
                    effect.AmbientLightColor = new Vector3(0.4f, 0.4f, 0.4f);

                    effect.Alpha = alpha;

                    effect.SpecularColor = new Vector3(0.95f, 0.95f, 0.95f);
                    effect.SpecularPower = 800f;

                    effect.Texture = texture;
                    effect.TextureEnabled = true;

                    // Note that this code will have to change if we start using Models that have multiple meshes, each transformed independently
                    // using bone transform action. For now, with models that have no animation, this will do.
                    effect.View = viewMatrix;
                    effect.Projection = projectionMatrix;
                    effect.World = worldTransform;
                }
                mesh.Draw();
            }
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            Graphics.GraphicsDevice.Clear(new Color(.4f,.4f,.4f));
            Graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
            Graphics.GraphicsDevice.PresentationParameters.MultiSampleType = MultiSampleType.FourSamples;
            Graphics.GraphicsDevice.RenderState.MultiSampleAntiAlias = true;

            // Draw all visible objects
            // TODO - group by model, then issue instanced draw commands with a list of world matrices - every Draw() call costs big bucks
            foreach (var obj in _visibleObjects)
            {
                DrawModel(obj);
            }

            DrawLines(gameTime);

            DrawPickedTriangle();

            DrawText();

            base.Draw(gameTime);
        }

        private void DrawLines(GameTime gameTime)
        {
            if (roundLineManager == null)
                return;

            roundLineManager.NumLinesDrawn = 0;

            const float lineRadius = 0.04f;
            roundLineManager.BlurThreshold = roundLineManager.ComputeBlurThreshold(lineRadius, projection,
                GraphicsDevice.PresentationParameters.BackBufferWidth);

            var time = (float)gameTime.TotalRealTime.TotalSeconds;
            string curTechniqueName = roundLineTechniqueNames[1];

            roundLineManager.Draw(roads, lineRadius, new Color(.6f,.4f,.2f), viewMatrix * projectionMatrix, time, curTechniqueName);
            roundLineManager.Draw(unObservedRoads, lineRadius, new Color(.3f, .2f, .1f), viewMatrix * projectionMatrix, time, curTechniqueName);
            roundLineManager.Draw(TempLines, lineRadius, Color.Green, viewMatrix * projectionMatrix, time, curTechniqueName);

            var moveColour = Color.Blue;
            moveColour.A = 192;

            roundLineManager.Draw(moveLines, lineRadius * 1.5f, moveColour, viewMatrix * projectionMatrix, time, roundLineTechniqueNames[2]);
        }

        private void DrawModel(VisibleObject obj)
        {
            DrawModel(obj.Model, obj.WorldTransform, obj.DiffuseColor, obj.Alpha, obj.Texture);
        }

    }
}
